// $codepro.audit.disable lineLength
package Controller;

import java.awt.Dimension;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JOptionPane;

import Model.CaptainsLog;
import Model.Event;
import Model.Market;
import Model.Planet;
import Model.Player;
import Model.TradeGood;
import Model.Universe;
import View.GUI;

/**
 * Coordinates between the model (game logic) and the view (GUI). Houses the main method and acts as
 * the go-between class which gives the game's classes any information needed from other classes
 * @author David Fredrick
 * @version 1
 */
public class Controller {
	
	/**
	 * initializes the main thread of the game and orchestrates the state of play, triggering the 
	 * proper GUI views based on the state of the game
	 * @return String
	 */
	
	public String toString(){
		String str = null;
		return str;
	}
	/**
	 * Main method
	 * @param args
	 */
	public static void main(String[] args){
		final Controller control = new Controller();
		
		control.setState(0); //represents the game's overall state
		
		//gameloop
		while (true){
			switch (control.getState()) {
			
			//Splash Screen - game initialization. creates or loads game based on user input
			case 0:
				control.triggerSplashScreen();
				control.setState(1);
				break;
				
			//Player Screen - main play window. houses most game options
			case 1:
				control.triggerPlayerScreen();
				break;
				
			//Save Triggered - saves the game and then triggers Player Screen
			case 2: // $codepro.audit.disable numericLiterals
				control.saveGame();
				control.setState(1);
				break;
			
			//Load Triggered - loads the saved game and then triggers Player Screen
			case 3: // $codepro.audit.disable numericLiterals
				control.loadGame();
				control.setState(1);
				break;
				
			//Marketplace Screen - displays buy/sell options
			case 4: // $codepro.audit.disable numericLiterals
				//try {
				
				//Tim edited this!
				if(market != null){
					control.triggerMarketScreen();
				}
				else{
					JOptionPane.showMessageDialog(null, "You must warp to a planet first!");
					control.setState(2); // $codepro.audit.disable numericLiterals
				}

				/*} catch (NullPointerException e) {
					control.setState(1);
					System.out.println("ERROR: NullPointerException");
				}*/
				break;
				
			//Warp Screen
			case 7: // $codepro.audit.disable numericLiterals
				control.triggerWarpScreen();
				control.setState(9); // $codepro.audit.disable numericLiterals
				break;
				
			//Dockbay Screen - displays upgrade/ship options
			case 8: // $codepro.audit.disable numericLiterals
				control.triggerDockScreen();
				break;
			
			//Event Screen - displays an event if there is one
			case 9: // $codepro.audit.disable numericLiterals
				control.triggerEventScreen();
				control.setState(1);
				break;
				
			//Exit
			case 100: // $codepro.audit.disable numericLiterals
				System.exit(0);
			
			default:
				break;
			}
			
		}//end gameloop
	}//end method main
	
	/**
	 * Planet variable
	 */
	private Planet planet;
	
	/**
	 * GUI variable
	 */
	private GUI gui = new GUI();
	
	/**
	 * Player variable
	 */
	private Player player;
	
	/**
	 * Universe variable
	 */
	private Universe universe;
	
	/**
	 * Market variable
	 */
	private static Market market;
	
	/**
	 * CaptainsLog variable
	 */
	private CaptainsLog log;
	
	/**
	 * FileInputStream used for loading files
	 */
	private FileInputStream inStream;
	
	/**
	 * ObjectInputStream used for loading objects from file
	 */
	private ObjectInputStream objectInputFile;
	
	/**
	 * FileOutputStream used for writing objects to a file
	 */
	private FileOutputStream outStream;
	
	/**
	 * ObjectOutputStream used for writing objects to a file
	 */
	private ObjectOutputStream objectOutputFile;
	
	/**
	 * Determines if there is a previously saved game saved already
	 */
	private boolean load;//tells the splash screen whether load game should be an option for the player
	
	/**
	 * State of the game
	 */
	private int state;
	
	/**
	 * Event variable
	 */
	Event event;
	
	/**
	 * houses the instantiated game logic and GUI. Passes information between them as needed.
	 */
	public Controller(){
		event = new Event();
		log = new CaptainsLog();
		//gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		gui.setPreferredSize(new Dimension(600, 675)); // $codepro.audit.disable numericLiterals
		gui.pack();
		gui.setVisible(true);
		
		try {
			inStream = new FileInputStream("Player.dat");
			//player already exists
			load = true;
		} catch (FileNotFoundException e) {
			//player does not already exist
			e.printStackTrace();
			load = false;
		}
		finally{
			try {
				inStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}//end constructor Controller
	
	/**
	 * collects new player data from the GUI and then instantiates a new player and a new universe for the game
	 */
	private void createGame(){
		final Object[] playerPacket = gui.getPlayerInfo();
		player = new Player((String) playerPacket[0], (int[]) playerPacket[2], (Boolean) playerPacket[1], (Integer) playerPacket[3]); // $codepro.audit.disable numericLiterals
		universe = new Universe();
		universe.inRange(player.getShipFuel());
		//displays a player panel for a TA demo
		//gui.statTest(player);
		saveGame();
	}//end method createGame
	
	/**
	 * loads pre-existing player and universe data from saved files
	 */
	private void loadGame(){
		try {
			//load player
			inStream = new FileInputStream("Player.dat");
			objectInputFile = new ObjectInputStream(inStream);
			player = (Player) objectInputFile.readObject();
			
			//load universe
			inStream = new FileInputStream("Universe.dat");
			objectInputFile = new ObjectInputStream(inStream);
			universe = (Universe) objectInputFile.readObject();
			
			//edited by Tim
			//load MarketPlace
			inStream = new FileInputStream("Market.dat");
			objectInputFile = new ObjectInputStream(inStream);
			market = (Market) objectInputFile.readObject();
			
			//load the captainsLog from file
			inStream = new FileInputStream("Log.dat");
			objectInputFile = new ObjectInputStream(inStream);
			log = (CaptainsLog) objectInputFile.readObject();
			
			//edited by Tim
			//loads current planet player is currently on
			planet = universe.getCurrentPlanet();
				
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		finally{
			try {
				inStream.close();
				objectInputFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//gui.statTest(player);
	}//end method loadGame
	
	/**
	 * writes the player and universe variables to data files so that they can be reloaded later
	 */
	private void saveGame(){
		try {
			//save player
			outStream = new FileOutputStream("Player.dat");
			objectOutputFile = new ObjectOutputStream(outStream);
			
			objectOutputFile.writeObject(player);
			//objectOutputFile.close();
			
			//save universe
			outStream = new FileOutputStream("Universe.dat");
			objectOutputFile = new ObjectOutputStream(outStream);
			objectOutputFile.writeObject(universe);
			//objectOutputFile.close();
			
			//save CaptainsLog
			outStream = new FileOutputStream("Log.dat");
			objectOutputFile = new ObjectOutputStream(outStream);
			objectOutputFile.writeObject(log);
			//objectOutputFile.close();
			
		} catch (FileNotFoundException e){
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		finally{
			try {
				objectOutputFile.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		//save market
		try {
			outStream = new FileOutputStream("Market.dat");
			objectOutputFile = new ObjectOutputStream(outStream);
			objectOutputFile.writeObject(market);
			objectOutputFile.close();
		} catch (FileNotFoundException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}//end method saveGame
	
	/**
	 * Gets current game state
	 * @return int
	 */
	public int getState(){
		return state;
	}//end getter getState
	
	/**
	 * Sets the current game state
	 * @param state
	 */
	public void setState(int state){
		this.state = state;
	}//end setter setState
	
	/**
	 * instructs GUI to display the splash screen and then collects user input. Then handles the
	 * cases chosen and calls the necessary methods
	 */
	private void triggerSplashScreen() {
		//calls some method from GUI which launches the splash screen
		final int splashChoice = gui.splashPop(load);
		//0=new game, 1=load game, 2=exit
		
		if (splashChoice == 0){					//creates a new game
			createGame();
		} else if (splashChoice == 1){			//loads a previously saved game
			loadGame();
		} else if (splashChoice == 2){			//exits the game // $codepro.audit.disable numericLiterals
			System.exit(0);
		} else {								//throws exception
			//throw exception
			System.out.println("ERROR: Controller recieved invalid value from gui.splashPop()");
		}
	}//end method triggerSplashScreen
	
	/**
	 * instructs GUI to display the main player screen from which traveling and stat viewing will 
	 * take place
	 */
	private void triggerPlayerScreen() {
		gui.setPreferredSize(new Dimension(600, 525)); // $codepro.audit.disable numericLiterals
		gui.setVisible(false);
		gui.pack();
		gui.validate();
		gui.setVisible(true);
		
		// current ship fuel, fuel capacity, techlevel (for screen)
		final int value = gui.playerPop(player.getShipFuel(), player.getShip().getFuelCap(), (planet == null ? 0 : planet.getTechLvl()), log);
		
		switch (value){
		//ship button
		case 1:
			break;
		//player button
		case 2:// $codepro.audit.disable numericLiterals
			break;
		//exit button
		case 3: // $codepro.audit.disable numericLiterals
			state = 100; // $codepro.audit.disable numericLiterals
			break;
		//save button
		case 4: // $codepro.audit.disable numericLiterals
			state = 2; // $codepro.audit.disable numericLiterals
			break;
		//warp button
		case 5:// $codepro.audit.disable numericLiterals
			state = 7;// $codepro.audit.disable numericLiterals
			break;
		//dock bay button
		case 6:// $codepro.audit.disable numericLiterals
			state = 8;// $codepro.audit.disable numericLiterals
			break;
		//marketplace button
		case 7:// $codepro.audit.disable numericLiterals
			state = 4;// $codepro.audit.disable numericLiterals
			break;
			
		default:
			break;
		}
	}//end method triggerPlayerScreen
	
	/**
	 * instructs GUI to display the marketplace screen
	 */
	
	private void triggerMarketScreen(){
		gui.setPreferredSize(new Dimension(600, 525));// $codepro.audit.disable numericLiterals
		gui.setVisible(false);
		gui.pack();
		gui.validate();
		gui.setVisible(true);
		
		final HashMap<String,TradeGood> marketStock = market.getMarketMap();
		HashMap<String,TradeGood> shipCargo = (HashMap<String, TradeGood>) player.getShipCargo();
		HashMap<String,Object> packet = gui.marketPop(marketStock, shipCargo, player.getShipMaxCargo(), player.getMoney());
			
		//packet: Button = value[0]
		//		  Quantity = value[3]
		//		  itemKey = item Key String
				
		//back to player screen
		if ((Integer) packet.get("Button") == -2) {// $codepro.audit.disable numericLiterals
			state = 1;
		//player has triggered a buy
		} else if ((Integer) packet.get("Button") == 0) {
			
			//String to be passed to the CaptainsLog
			final int cost = marketStock.get((String) packet.get("itemKey")).getBaseCost();
			gui.setMarketInteractions("Bought " + (Integer) packet.get("Quantity") + " " + (String) packet.get("itemKey") + " for " + cost + " each"); 
			
			final Map<String, Object> buyPacket = market.buyItem((String) packet.get("itemKey"), player.getCurrentCargo(), player.getShipMaxCargo(), player.getMoney(), (Integer) packet.get("Quantity"), shipCargo);
			
			player.setCurrCargo((Integer) buyPacket.get("Current Cargo"));
			player.setMoney((Integer) buyPacket.get("Player Money"));
			player.setCargo((HashMap<String,TradeGood>) buyPacket.get("Cargo"));
			
		//player has triggered a sell
		} else if ((Integer) packet.get("Button") == 1) {
			final Map<String, Object> sellPacket = market.sellItem((String) packet.get("itemKey"), (HashMap<String, TradeGood>) player.getShipCargo(), player.getCurrentCargo(), player.getMoney(), (Integer) packet.get("Quantity"));
			
			//String to be passed to the CaptainsLog
			gui.setMarketInteractions("Sold " + (Integer) packet.get("Quantity") + " " + (String) packet.get("itemKey") + " for " + marketStock.get((String) packet.get("itemKey")).getBaseCost() + " each.");
					
			player.setMoney((Integer) sellPacket.get("Player Money"));
			player.setCurrCargo((Integer) sellPacket.get("Current Cargo"));
			player.setCargo((HashMap<String,TradeGood>) sellPacket.get("Player Goods"));
		}
	}//end method triggerMarketScreen
	
	/**
	 * instructs GUI to display the dockbay screen
	 */
	private void triggerDockScreen(){
		System.out.println("Do you run");
		gui.setPreferredSize(new Dimension(600, 525));// $codepro.audit.disable numericLiterals
		gui.setVisible(false);
		gui.pack();
		gui.validate();
		gui.setVisible(true);
		
		int[] value = gui.dockPop(player.getShipFuel(), player.getShipFuelCap());
		
		if (value[0] == 1) {
			try {
				player.setMoney(player.getMoney() - (player.getShipFuelCap() - player.getShipFuel()));
				player.setShipFuel(player.getShipFuelCap());
			} catch (Exception e) {
				e.printStackTrace();
				System.out.println("You don't have enough money");
			}
			state = 8;// $codepro.audit.disable numericLiterals
		} else if (value[0] == 2) {// $codepro.audit.disable numericLiterals
			state = 1;
		}
		
	}//end method triggerDockScreen
	
	/**
	 * instructs GUI to display the warp window
	 */
	private void triggerWarpScreen(){
		gui.setPreferredSize(new Dimension(600, 525));// $codepro.audit.disable numericLiterals
		gui.setVisible(false);
		gui.pack();
		gui.validate();
		gui.setVisible(true);
		
		universe.inRange(player.getShipFuel());

		int[] temp = universe.getPlayerLoc(); //holder for initial player loc before warp
		int[] loc = new int[4];// $codepro.audit.disable numericLiterals
		loc[0] = temp[0];
		loc[1] = temp[1];
		//for total fuel expended to be calculated
		universe.inRange(player.getShipFuel());
	
		int[] value = gui.warpPop(universe.getUniverse(), universe.getPlayerLoc(), player.getShipFuel(), player.getShipFuelCap());
		if (value[0] != -2) {// $codepro.audit.disable numericLiterals
			//sets player location
			universe.setPlayerLoc(value[0], value[1]);
			
			//resets the market
			planet = universe.getCurrentPlanet();
			market = new Market(planet.getTechLvl(), planet.getGovType(), planet.getState());
			market.generateItemsSold();
			market.generateItemsDemanded();
			
			//save market
			try {
				outStream = new FileOutputStream("Market.dat");
				objectOutputFile = new ObjectOutputStream(outStream);
				objectOutputFile.writeObject(market);
				objectOutputFile.close();
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		int[] temp2 = universe.getPlayerLoc();//new player location to use in calculating ship 
		// distance
		
		double distTraveled = Math.sqrt(Math.pow(temp2[1] - loc[1], 2) + Math.pow(temp2[0] - loc[0], 2));// $codepro.audit.disable numericLiterals
		distTraveled = distTraveled * (.5);// $codepro.audit.disable numericLiterals
		player.getShip().setFuel((player.getShip().getFuel() - (int) distTraveled)); //daannng thats ugly // $codepro.audit.disable lossOfPrecisionInCast
		// subtracts distance traveled fuel from ship
		
	}//end method triggerWarpScreen
	
	/**
	 * TriggerEventScreen method.
	 */
	private void triggerEventScreen(){
		gui.setPreferredSize(new Dimension(400, 400));// $codepro.audit.disable numericLiterals
		gui.setVisible(false);
		gui.pack();
		gui.validate();
		gui.setVisible(true);
		
		HashMap<String,Object> eventPacket = (HashMap<String, Object>) event.nextEvent();
		System.out.println("value: " + eventPacket.get("value"));

		switch((Integer) eventPacket.get("value")){
		case 0:
			break;
		case 1://market
			//TODO add trade goods to shipCargo (particular TradeGood with quantity found in HashMap)
			TradeGood tg = (TradeGood) eventPacket.get("tradegood");
			if(tg.getQuantity() > (player.getShipMaxCargo() - player.getCurrentCargo())){
				tg.setQuantity(player.getShipMaxCargo() - player.getCurrentCargo());
			}
			player.setCurrCargo(player.getCurrentCargo() + tg.getQuantity());
			if(player.getShipCargo().get(tg.getName()) == null){
				player.getShipCargo().put(tg.getName(), tg);
			} else {
				player.getShipCargo().get(tg.getName()).setQuantity(player.getShipCargo().get(tg.getName()).getQuantity() + tg.getQuantity());
			}
			gui.eventPop((Integer) eventPacket.get("value"), (String) eventPacket.get("message"), (String) eventPacket.get("title"));
			break;
		case 2://// $codepro.audit.disable numericLiterals
			if (player.getMoney() > 100){// $codepro.audit.disable numericLiterals
				player.setMoney(player.getMoney() - 100);// $codepro.audit.disable numericLiterals
			} else {
				player.setMoney(0);
			}
			gui.eventPop((Integer) eventPacket.get("value"), (String) eventPacket.get("message"), (String) eventPacket.get("title"));
			break;
		case 3:// $codepro.audit.disable numericLiterals
			int[] skills = player.getSkills();
			for (int i=0;i < skills.length;i++){
				skills[i] += 1;
			}
			player.setSkills(skills);
			gui.eventPop((Integer) eventPacket.get("value"), (String) eventPacket.get("message"), (String) eventPacket.get("title"));
			break;
			
		default:
			break;
		}
	}//end method triggerEventScreen
}//end class Controller
